package com.git.hui.yinshua.common.id;

import com.git.hui.yinshua.common.DateUtil;
import com.git.hui.yinshua.common.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.time.LocalDateTime;

/**
 * 自定义实现的雪花算法生成器
 * <p>
 * 时间 + 机器id(8位) + 序列号(5位)
 *
 * @author YiHui
 * @date 2024/06/11
 */
@Slf4j
public class SnowflakeIdGenerator {
    /**
     * 自增序号位数 1s最多32个
     */
    private static final long SEQUENCE_BITS = 5L;

    /**
     * 机器位数
     */
    private static final long WORKER_ID_BITS = 8L;

    private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;

    private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;
    /**
     * 机器id (8位, 最多255台机器） 默认基于内网ip的最后一组来获取
     */
    private long workId = 1;

    /**
     * 上次的访问时间
     */
    private long lastTime;
    /**
     * 自增序号
     */
    private long sequence;

    private byte sequenceOffset;

    public SnowflakeIdGenerator() {
        try {
            String ip = IpUtil.getLocalIp4Address();
            String[] cells = StringUtils.split(ip, ".");
            this.workId = Integer.parseInt(cells[3]) & ((1 << WORKER_ID_BITS) - 1);
        } catch (Exception e) {
            this.workId = 1;
        }
    }

    public SnowflakeIdGenerator(int workId) {
        this.workId = workId;
    }

    /**
     * 生成趋势自增的id
     *
     * @return
     */
    public synchronized Long nextId() {
        long nowSeconds = waitToIncrDiffIfNeed(getNowTime());
        if (lastTime == nowSeconds) {
            if (0L == (sequence = (sequence + 1) & SEQUENCE_MASK)) {
                // 表示当前这一时刻的自增数被用完了；等待下一时间点
                nowSeconds = waitUntilNextTime(nowSeconds);
            }
        } else {
            // 上一秒若以0作为序列号开始值，则这一秒以1为序列号开始值
            vibrateSequenceOffset();
            sequence = sequenceOffset;
        }
        lastTime = nowSeconds;
        long ans = (workId << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
        return Long.parseLong(String.format("%s%04d", getDaySegment(nowSeconds), ans));
    }

    /**
     * 若当前时间比上次执行时间要小，则等待时间追上来，避免出现时钟回拨导致的数据重复
     *
     * @param nowSeconds 当前时间戳（秒）
     * @return 返回新的时间戳
     */
    private long waitToIncrDiffIfNeed(final long nowSeconds) {
        if (lastTime <= nowSeconds) {
            return nowSeconds;
        }
        long diff = lastTime - nowSeconds;
        sleep(diff * 1000L);
        return getNowTime();
    }

    public static void sleep(long millis) {
        if (millis > 0L) {
            try {
                Thread.sleep(millis);
            } catch (InterruptedException var3) {
            }
        }
    }

    /**
     * 等待下一秒
     *
     * @param lastTime
     * @return
     */
    private long waitUntilNextTime(final long lastTime) {
        long result = getNowTime();
        while (result <= lastTime) {
            result = getNowTime();
        }
        return result;
    }

    private void vibrateSequenceOffset() {
        sequenceOffset = (byte) (~sequenceOffset & 1);
    }


    /**
     * 获取当前时间
     *
     * @return 秒为单位
     */
    private long getNowTime() {
        return System.currentTimeMillis() / 1000;
    }

    /**
     * 基于年月日构建分区
     *
     * @param time 时间戳
     * @return 时间分区
     */
    private static String getDaySegment(long time) {
        LocalDateTime date = DateUtil.time2LocalTime(time * 1000L);
        int year = date.getYear() % 100;
        int month = date.getMonthValue();
        int day = date.getDayOfMonth();
        int hour = date.getHour();
        int min = date.getMinute();
        int seconds = date.getSecond();
        return year + (month < 10 ? "0" : "") + month + (day < 10 ? "0" : "") + day
                + (hour < 10 ? "0" : "") + hour
                + (min < 10 ? "0" : "") + min
                + (seconds < 10 ? "0" : "") + seconds;
    }
}
